'''
Created on 1 nov 2009

@author: fredrik
'''
from gameobjects import GameObject,AnimatedGameObject,vel_limit
from common import Tick,BounderyVal
from pygame import Surface
from math import atan,atan2,sin,cos,pi,hypot
from weapons import Shot

#----------- Ammo types --------------------
class Enemy(AnimatedGameObject):
    create_sound = None
    explode_sound = None
    syncable = True
    mass = 150
    def __init__(self,game_engine,pos_x,pos_y,vel_x,vel_y,owner_id=None, id=None,power=1.6,mass=None):
        AnimatedGameObject.__init__(self,game_engine,pos_x,pos_y,vel_x,vel_y, id=id)
        self.old_pos_x,self.old_pos_y = self.pos_x,self.pos_y
        self.power = power
        self.owner_id = owner_id
        self.alive_time = 0
        self.max_alive_time = 0
        self.changed = False
        self.landed = False
        self.collide = False
        self.crashed = False
        self.last_hit_by = None
        self.anim_modes = {}
        self._mode = WAITING
        self.life = BounderyVal(0,25,25)    # life points
        if mass: self.mass = mass
#        self.mover = mover
        self.sp = game_engine.soundplayer

    def explode(self):
        self.sp.play(self.explode_sound,0,0)

    def changeLife(self,dlife):
        ' Changes life points, die if life < 0'
        changed = self.life.change(dlife)
        self.changed |= changed
        return changed

    def checkCollide(self):
        self.old_collide = self.collide
        self.collide = self.game_map.rectCollide(self.rect)
        self.cur_tile_pos = self.game_map.getMapXYAtPos(self.rect.center[0],self.rect.center[1])
        self.crashed = self.collide
        
    def update(self,dt):
        AnimatedGameObject.update(self,dt)
        self.alive_time += dt
        self.move(dt)
        self.checkCollide()

    def kill(self):
        'make ship explode if it is killed '
        self.explode()
        super(AnimatedGameObject,self).kill()

    def _get_mode(self,mode):
        return self._mode
    def _set_mode(self,mode):
        if self._mode != mode:
            self._mode = mode
            self.animation = animations[self.__class__][mode]
            self.time = 0
    mode = property(_get_mode, _set_mode)

animations = {}
movers = {}

from gameobjects import Animation
from common import load_image
import random
class Invader(Enemy):
    def __init__(self,*args,**kwargs):
        Enemy.__init__(self,*args,**kwargs)
        
    def explode(self):
        vel = 300
        rad_deg = pi/180
        self.sp.play(self.explode_sound,0,1)
        addShot = self.game_engine.addShot
        # create a cluster of Shots
        for deg in range(0,360,16):
            dir_x,dir_y = (sin(deg*rad_deg), cos(deg *rad_deg) )
            addShot(Shot(self.game_engine,self.rect.center[0],self.rect.center[1],
                         self.vel_x+vel*dir_x,self.vel_y+vel*dir_y,
                         power=3,owner_id=None), local=0,nosound=1) # local=0 dont tell clients

    def move(self,dt):
        if self.mover is not None:
            self.mover.move(self,dt)
        super(Invader,self).move(dt)
#    def __init__(self,game_engine,pos_x,pos_y,vel_x,vel_y,owner_id=None, id=None,power=1.6):
#        super(Enemy,self).__init__(game_engine,pos_x,pos_y,vel_x,vel_y, id=id,power=power)

class InvaderStalker(Invader):
    collide_damage_ratio = 0.001 # game world map collision speed to damage ratio
    bounce_ratio = 0.6 # game world map collide bounce back  ratio
    def __init__(self,*args,**kwargs):
        global animations,movers

        try:
            self.animation = animations[self.__class__][WAITING]
        except KeyError:
            anim_img = load_image('data/bmp/ship5_colorkey.bmp', (255,0,255))
            self.animation = Animation(anim_img,tile_width=23,tile_height=24,fps=0,spacing=1,margin=1)
            animations[self.__class__] = {}
            animations[self.__class__][WAITING] = self.animation
            animations[self.__class__][MOVING] = Animation(anim_img,tile_width=23,tile_height=24,fps=10,spacing=1,margin=1)
            print self.animation
        Invader.__init__(self,*args,**kwargs)
        # TODO : Do not forget to empyt the global movers dict when new game starts.
        self.mover = movers[self.__class__]
        # to make animations of same object not be synced all the time randomize the starting time.
        # randomize anim 0-5 seconds.
        self.time = random.random()*5  
#        if not self.mover:
#            self.mover = HomingMover(self.game_engine)

class InvaderHunter(Invader):
    
    def __init__(self,*args,**kwargs):
        global animations,movers
        try:
            self.animation = animations[self.__class__][WAITING]
        except KeyError:
            anim_img = load_image('data/bmp/ship3_colorkey.png', (255,0,255))
            animations[self.__class__] = {}
            self.animation = Animation(anim_img,tile_width=23,tile_height=24,fps=0,spacing=1,margin=1)
            animations[self.__class__][WAITING] = self.animation
            animations[self.__class__][MOVING] = Animation(anim_img,tile_width=23,tile_height=24,fps=5,spacing=1,margin=1)
            print self.animation
        Invader.__init__(self,*args,**kwargs)
        # TODO : Do not forget to empy the global movers dict when new game starts.
        self.mover = movers[self.__class__]
        # to make animations of same object not be synced all the time randomize the starting time.
        # randomize anim 0-5 seconds.
        self.time = random.random()*5  
#        if not self.mover:
#            self.mover = HomingMover(self.game_engine)

class InvaderMine(Invader):
    collide_damage_ratio = 0.005 # game world map collision speed to damage ratio
    bounce_ratio = 0.8 # game world map collide bounce back  ratio
    image_file = 'data/bmp/SmallMine.png'
    def __init__(self,*args,**kwargs):
        global animations,movers
        
        try:
            self.animation = animations[self.__class__][WAITING]
        except KeyError:
            anim_img = load_image(self.image_file, (255,0,255))
            animations[self.__class__] = {}
            self.animation = Animation(anim_img,tile_width=24,tile_height=24,fps=0,spacing=0,margin=0)
            animations[self.__class__][WAITING] = self.animation
            animations[self.__class__][MOVING] = self.animation
            print self.animation
        Invader.__init__(self,*args,**kwargs)
        self.mass = 2500
        self.life = BounderyVal(0,150,150)    # life points
        
        # TODO : Do not forget to empyt the global movers dict when new game starts.
        self.mover = movers[self.__class__]
        # to make animations of same object not be synced all the time randomize the starting time.
        # randomize anim 0-5 seconds.
        self.time = random.random()*5  
#        if not self.mover:
#            self.mover = HomingMover(self.game_engine)

class InvaderInvertedMine(InvaderMine):
    image_file = 'data/bmp/BlackHole.png'
    collide_damage_ratio = 0.0005 # game world map collision speed to damage ratio
    bounce_ratio = 0.8 # game world map collide bounce back  ratio

MOVING = 1
WAITING = 0
class Mover(object):
    def __init__(self,game_engine):
        self.game_engine = game_engine
        self.game_map = game_engine.game_map


    def velLimit(self,go):
        ' limit velosity to max_vel '
        go.vel_x,go.vel_y = vel_limit(go.vel_x,go.vel_y,self.max_vel)
        
class HomingMover(Mover):
    def __init__(self,game_engine):
        super(HomingMover,self).__init__(game_engine)
        self.max_vel = 100*game_engine.relative_speed
        self.v_change = 100*game_engine.relative_speed
        self.lock_radius = 200*game_engine.relative_speed
        self.go_targets = {}
        self.target = None
        
    def move(self,go,dt):
        x,y = go.pos_x,go.pos_y
        target = self.go_targets.get(go,None)
        # lock on nearest, inreach and alive target
        min_r = None
        for s in self.game_engine.getShips():
            if s.alive():
                r = hypot(x-s.pos_x, y-s.pos_y) 

                if (min_r == None or r<min_r):
                    min_r=r
                    if self.lock_radius > r:
                        target = s

        if target and target.alive():
            # persue the target
            s = target
            vd = self.v_change
            dx = -(x-s.pos_x)/min_r
            dy = -(y-s.pos_y)/min_r
            go.changeVel(dx*vd,dy*vd,dt)
            self.velLimit(go)
            go.mode = MOVING
        else:
            target = None
            # if missile armed and has no lock, slow down
            go.decreaseVel(40,dt)
            go.mode = WAITING
    
        self.go_targets[go] = target

from time import time
import astar
from operator import attrgetter

class PathMover(Mover):
    def __init__(self,game_engine):
        super(PathMover,self).__init__(game_engine)
        self.game_map = self.game_engine.game_map
        self.max_vel = 100.0*game_engine.relative_speed
        self.v_change = self.max_vel*0.15
        self.lock_radius = 300*game_engine.relative_speed
        self.last_hit_by = None
        self.go_target_routes = {}
        self.route = None
        self.target = None

        
        width,height = self.game_map.getSize()
        _map = self.game_map.map
        nodes = [ [astar.Node(blocked=int(bool(_map[y][x]))) for y in xrange(height)] for x in xrange(width)]
        self._astar = astar.Astar(nodes, width, height,search_limit=500)
        

    def getBFSRoute2Target(self,go,target):
        'Breadth First Search (BFS)'
        from gamemap import find_route
        target_coord = self.game_map.getMapXYAtPos(*target.rect.center)
        go_coord = self.game_map.getMapXYAtPos(*go.rect.center)
        return find_route(self.game_map,go_coord,target_coord)

    def getAstarRoute2Target(self,go,target):
        target_coord = self.game_map.getMapXYAtPos(*target.rect.center)
        go_coord = self.game_map.getMapXYAtPos(*go.rect.center)
        route = self._astar.find_path(go_coord, target_coord)
        getcoord = attrgetter('coord')
        return route if route is None else [getcoord(n) for n in reversed(route)]

    def move(self,go,dt):
        x,y = go.pos_x,go.pos_y
        # get existing route
        target_route = self.go_target_routes.get(go,None)
        if not target_route:
            # no route calculated yet
            # lock on nearest, inreach and alive target and calculate route to it
            min_r = None
            for s in self.game_engine.getShips():
                if s.alive():
                    r = hypot(x-s.pos_x,y-s.pos_y)
                    if (min_r == None or r<min_r):
                        min_r=r
                        if self.lock_radius > r:
                            stime = time()
                            try:
                                route = self.getAstarRoute2Target(go,s)
                            except astar.PathSearchLimitException:
                                print "path search exeeded"
                                route = None
                            target_route = (s,route)
                            print "path calc time:",time()-stime
                            if target_route[1]: print "path len:",len(target_route[1])
                            self.go_target_routes[go] = target_route
        if target_route:
            target,route = target_route
        else:
            target,route = None, None
        if route:# and target.alive():
            # persue the target
            next_coord = route[0]
            if (next_coord == self.game_map.getMapXYAtPos(*go.rect.center)):
                next_coord = route.pop(0)
                #print "next coord",next_coord
            # calculate the pixel cords to the next tile in path
            next_x,next_y =(int((next_coord[0] * self.game_map.tile_w)+self.game_map.tile_w/2),int((next_coord[1] * self.game_map.tile_h)+self.game_map.tile_h/2)) 
            vd = self.v_change
            dx = -(x-next_x)
            dy = -(y-next_y)
            go.changeVel(dx*vd,dy*vd,dt)
            self.velLimit(go)
            go.mode = MOVING
        elif route is not None:
            # we reached the target tile so reset the target
            # so new route will be calculated next time
            go.mode = WAITING
            del self.go_target_routes[go]
            # if missile armed and has no lock, slow down
            go.decreaseVel(40,dt)
        else:
            # did not find a route to target for this game object
            # so do not try to recalc it as it could be very slow.
            go.mode = WAITING
            go.decreaseVel(40,dt)


import math
def gravity(go1,go2,dt,repell=False,both=False,force=1.0):
    grav_multi = 10.0
    sx,sy = go2.pos_x,go2.pos_y
    x,y = go1.pos_x,go1.pos_y
    dist = hypot(x-sx,y- sy)/250.0
    rad = -atan2((sy - y),(sx - x))- pi/2
    dx = - (dist**(-2))*sin(rad)*grav_multi*force
    dy = - (dist**(-2))*cos(rad)*grav_multi*force
    if repell:
        dx,dy = -dx,-dy
    go1.changeVel(dx,dy,dt)
    if both:
        go2.changeVel(-dx,-dy,dt)

class GravityMover(Mover):
    def __init__(self,game_engine,repell=False,both=False,force=1.0):
        super(GravityMover,self).__init__(game_engine)
        self.force = force
        self.both = both
        self.repell = repell
        self.max_vel = 100*game_engine.relative_speed
        self.v_change = 100*game_engine.relative_speed
        self.lock_radius = 500*game_engine.relative_speed
        self.go_targets = {}
        self.target = None
    
        
    def move(self,go,dt):
        x,y = go.pos_x,go.pos_y
        target = self.go_targets.get(go,None)
        # lock on nearest, inreach and alive target
        min_r = None
        for s in self.game_engine.getShips():
            if s.alive():
                r = hypot(x-s.pos_x, y-s.pos_y) 
                if (min_r == None or r<min_r):
                    min_r=r
                    if self.lock_radius > r:
                        target = s

        if target and target.alive():
            # persue the target
            s = target
            vd = self.v_change
            gravity(go,target,dt,repell=self.repell,both=self.both,force=self.force)
        else:
            target = None
            go.mode = WAITING
            # if missile armed and has no lock, slow down
            go.decreaseVel(40,dt)
    
        self.go_targets[go] = target

class InvaderMover(Mover):
    def __init__(self,game_engine):
        super(InvaderMover,self).__init__(game_engine)
        self.movers = (PathMover(game_engine),HomingMover(game_engine))

    def move(self,go,dt):
        for mover in self.movers:
            mover.move(go,dt)
